package wsi.resolve;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.rygh.core.html.Html;
import org.rygh.core.html.table.HtmlTable;
import org.rygh.core.html.table.TableData;
import org.rygh.core.html.table.TableTuple;
import org.rygh.core.model.common.MapList;
import org.rygh.core.util.UIo;
import org.rygh.core.util.counter.SimpleCounter;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Fact;
import org.rygh.semantic.model.rdf.Predicate;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlTree;
import wsi.exp.Exp2TreeToEntity;
import wsi.exp.ExpFolderController;
import wsi.exp.exp3.Exp3PathMatchByPair;
import wsi.exp.exp3.PathMatch;
import wsi.function.DirManager;
import wsi.function.Function.Phrase;
import wsi.fusion.VariableManager;
import wsi.gui.tool.DBInjector;
import wsi.match.MatchProperty;
import wsi.match.SubtreeMatch;
import wsi.match.SubtreeMatcher;
import wsi.script.AppXml;
import wsi.subgraph.entity.EntityGroup;
import wsi.subgraph.entity.EntityGroupController;
import wsi.subgraph.entity.EntityResolve;
import wsi.subgraph.entity.ResolvePair;
import wsi.subgraph.tree.EntityLeaf;
import wsi.subgraph.tree.EntityTree;

/**
 * The class which controls some general and macro aspects regarding to
 * resolution. Specific resolution is handled by Resolver
 * 
 * @author nanmus
 * 
 */
public class ResolveController extends SubtreeMatcher {

	private final static String[] SAFE_PRUNE_TAGS = { "y:wordnet",
			"y:wikicategory", "y:male", "y:female" };

	private final Map<Input, FactSet> factGroupMap = new HashMap<>();

	private final Map<WSXmlLeaf, SubtreeMatch> allMatchMap = new HashMap<>();
	private final MapList<Input, ResolvePair> oneMaxPairList = new MapList<>();
	private final MapList<Input, ResolvePair> allMaxPairList = new MapList<>();
	private final MapList<Input, WSXmlLeaf> candidateSubrootMapList = new MapList<>();

	protected final Aggregator aggregator;
	protected final Resolver resolver;

	private final PCAConfidence pcaConfidence = new PCAConfidence();

	protected MatchProperty matchProperty;

	public ResolveController(DirManager dir) {
		super(dir);

		aggregator = new Aggregator(dir);
		aggregator.setMatchMap(matchMap);

		resolver = new Resolver(dir.getDataGuide(), subtreeController);
	}

	@Override
	public void compute() {
		// log.info("computing");
		super.compute();

		resolve();

		// subrootMapList is ready after super computes.
		aggregator.setSubrootMapList(subrootMapList);
		aggregator.aggregate(oneMaxPairList, allMaxPairList);
		aggregator.computeMatch();

		save();

		//
		generate();
		computePCAConfidence();

		resolver.compensate(subrootMapList, entityTreeMap);

		//
		resolver.initFusion();
		instantiate();

		saveExperiments();
	}

	/**
	 * 13-12-07<br>
	 * <br>
	 * 
	 * PCA confidence should be computed before compensation. <br>
	 * <br>
	 * 
	 * note that PCA is not affected by fusion. according to the definition of
	 * pca, it is an aggregation of all entities with certain constraints.
	 * 
	 * @param subrootMapList
	 */
	private void computePCAConfidence() {
		pcaConfidence.setFactItemCounter(resolver.getFactItemCounter());
		pcaConfidence.setSubrootMapList(subrootMapList);

		pcaConfidence.setAllMaxPairList(allMaxPairList);
		pcaConfidence.setOneMaxPairList(oneMaxPairList);

		pcaConfidence.setMatchProperty(matchProperty);
		pcaConfidence.setEntityTreeMap(entityTreeMap);

		pcaConfidence.compute();
	}

	public void define() {
		FunctionDefiner definer = new FunctionDefiner(dir, subtreeController,
				resolver, matchProperty);

		definer.generate();
		definer.save();
	}

	private void generate() {
		System.out.println("generating");

		matchProperty = aggregator.getMatchProperty();

		resolver.setMatchProperty(matchProperty);
		candidateSubrootMapList.clear();

		switch (matchProperty.getType()) {
		case EXISTS:
			generateExistsMatch();
			break;

		case FORALL:
			generateForallMatch();
			break;
		}

		generateForUnmatched();

	}

	private void generateExistsMatch() {
		List<WSXmlLeaf> subroots;
		Input input;

		int index = matchProperty.getSubtreeIndex();

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
			input = e.getKey();
			subroots = e.getValue();

			if (subroots.size() < index)
				continue;

			WSXmlLeaf good_root = subroots.get(index - 1);
			assert good_root.getXpath().getLastIndex() == index : String
					.format("%d != %d", good_root.getXpath().getLastIndex(),
							index);

			candidateSubrootMapList.put(input, good_root);
		}

		rematch();

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
			input = e.getKey();
			subroots = e.getValue();

			EntityTree etree = entityTreeMap.get(input);

			if (subroots.size() < index)
				continue;

			WSXmlLeaf good_root = subroots.get(index - 1);
			assert good_root.getXpath().getLastIndex() == index : String
					.format("%d != %d", good_root.getXpath().getLastIndex(),
							index);

			if (!matchMap.containsKey(good_root)) {
				log.info(String.format(
						"[warn] input %s: no match info for subtree %s", input,
						good_root.getXpath()));

				continue;
			}

			SubtreeMatch sm = matchMap.get(good_root);
			resolver.setSubtreeMatch(sm);
			resolver.setEntityTree(etree);
			resolver.generate();
		}

	}

	/**
	 * generate for Forall match with one-max list (for a set of subtrees T and
	 * an entity group G, among all (tree, entity) pairs in T * G, select the
	 * pair with the highest probability. (One candidate for T * G).
	 */
	private void generateForallMatch() {

		/*
		 * 13-09-16. if we only process good resolve pairs. consider this case:
		 * 
		 * a match (leaf, literal) is detected in a leaf, which is not in a good
		 * resolve, however, this match will make the leaf non-unmatched. for
		 * example author_id is matched to only one hasFamilyName. when we
		 * generate only good resolve pairs, the author_id will appear neither
		 * in matched nor unmatched group.
		 * 
		 * the remedy is to perform subtree match only on good resolve pairs
		 * before generating fact triples.
		 */

		for (Entry<Input, List<ResolvePair>> e : oneMaxPairList.entrySet()) {
			Input input = e.getKey();
			List<ResolvePair> pairs = e.getValue();

			// System.out.println("\ncomputeForallMatch input " + input);
			// System.out.println("  computeForallMatch pred " + path.getKey());

			/*
			 * like in exists case, we resolve only the best subtree, for forall
			 * case, we only resolve the best pair of all between a predicate
			 * path and the subtrees.
			 */

			for (ResolvePair pair : pairs) {
				EntityLeaf node = pair.getEntityLeaf();

				if (!matchProperty.validateMatchedPath(node))
					continue;

				candidateSubrootMapList.put(input, pair.getXmlLeaf());
			}
		}

		// store matchMap as it will be cleared in rematch.
		allMatchMap.putAll(matchMap);
		rematch();

		for (Entry<Input, List<ResolvePair>> e : oneMaxPairList.entrySet()) {
			Input input = e.getKey();
			List<ResolvePair> pairs = e.getValue();
			EntityTree etree = entityTreeMap.get(input);

			// System.out.println("\ncomputeForallMatch input " + input);
			// System.out.println("  computeForallMatch pred " + path.getKey());

			/*
			 * like in exists case, we resolve only the best subtree, for forall
			 * case, we only resolve the best pair of all between a predicate
			 * path and the subtrees.
			 * 
			 * 13-12-07 for each input, each entity group, there is only one
			 * (subtree, entity) pair with max prob. (one-max).
			 */

			for (ResolvePair pair : pairs) {
				EntityLeaf node = pair.getEntityLeaf();

				if (!matchProperty.validateMatchedPath(node))
					continue;

				WSXmlLeaf root = pair.getXmlLeaf();

				// System.out.println("  computeForallMatch root "
				// + root.getXpath());
				// System.out.printf("  -> resolve good pair %s (%s) <> %s\n",
				// node.getValue().getValue(), node.getValue()
				// .getPredicate(), root.getXpath().getValue());

				SubtreeMatch sm = matchMap.get(root);
				resolver.setSubtreeMatch(sm);
				resolver.setEntityTree(etree);
				resolver.setCenterNode(node);
				resolver.generate();
			}
		}
	}

	private void generateForUnmatched() {
		WSXmlLeaf common = subtreeController.getCommonRoot();

		SimpleCounter<WSXmlLeaf> unmatchedCounter = subtreeCounter
				.getUnmatchedCounter();

		for (WSXmlLeaf leaf : unmatchedLeaves) {
			assert unmatchedCounter.contains(leaf);

			XmlPath p = leaf.getXpath().getRelativePathsTo(common.getXpath());

			Predicate pred = new Predicate(p.toPredicateString(), true);

			// System.out.println("unmatched leaf " + p.getValue());
			// System.out.printf("unmatchedCounter %d %s\n",
			// unmatchedCounter.get(l), l.getXpath());

			resolver.addUnmatchedItem(leaf, pred, unmatchedCounter.get(leaf));
		}

		resolver.getTmpFactItemCounter().setNormalizeFactor(
				aggregator.getSubtreeCount());
	}

	/**
	 * <s>for FORALL match: like in exists case, we instantiate only the best
	 * subtree, for forall case, we only instantiate the best pair of all
	 * between a predicate path and the subtrees. note that when facts are
	 * composed against generated XML and XSL, all subtrees are treated
	 * equally.</s><br>
	 * <br>
	 * 
	 * 13-10-30 <br>
	 * delete the above. when instantiating, for FORALL match, we instantiate
	 * all subtrees, for experiment 2b (tree to instance match).<br>
	 * <br>
	 * 
	 * 13-12-07 <br>
	 * instantiate for Forall match with all-max list (for a set of subtrees T
	 * and an entity group G, for each tree t in T, among all (t, entity) pairs
	 * in t * G, select the pair with the highest probability. (One candidate
	 * for each subtree t).
	 */
	private void instantiate() {
		System.out.println("instantiating");

		switch (matchProperty.getType()) {
		case EXISTS:
			instantiateExistsMatch();
			break;

		case FORALL:
			instantiateForallMatch();
			break;
		}
	}

	private void instantiateExistsMatch() {
		List<WSXmlLeaf> subroots;
		WSXmlLeaf subroot;
		Input input;

		EntityTree etree;
		SubtreeMatch sm;

		FactSet group;

		int index = matchProperty.getSubtreeIndex();

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
			input = e.getKey();
			subroots = e.getValue();

			if (subroots.size() < index)
				continue;

			subroot = subroots.get(index - 1);
			assert subroot.getXpath().getLastIndex() == index : String.format(
					"%d != %d", subroot.getXpath().getLastIndex(), index);

			sm = matchMap.get(subroot);

			if (sm == null) {
				System.out
						.println("[warn.instantiate] no subtree match for root "
								+ subroot.getXpath());
				continue;
			}

			sm.setXmlFunctionalityController(xmlFunctionalityCtrl);

			etree = entityTreeMap.get(input);

			if (etree == null) {
				System.out
						.println("[warn.instantiate] no entity tree for input "
								+ input);
				continue;
			}

			resolver.setSubtreeMatch(sm);
			resolver.setEntityTree(etree);

			group = resolver.instantiate(input, subroot);
			factGroupMap.put(input, group);
		}
	}

	private void instantiateForallMatch() {
		WSXmlLeaf subroot;
		EntityTree etree;
		SubtreeMatch sm;
		FactSet group;
		Input input;

		for (Entry<Input, List<ResolvePair>> e : allMaxPairList.getMap()
				.entrySet()) {
			input = e.getKey();
			List<ResolvePair> pairs = e.getValue();

			// log.info("to instantiate for input " + input);

			/*
			 * <s>like in exists case, we resolve only the best subtree, for
			 * forall case, we only resolve the best pair of all between a
			 * predicate path and the subtrees.</s>
			 * 
			 * 13-10-30, instantiate all subtrees equally, for exp2b.
			 * 
			 * 13-12-07 for each input and each subtree, for each entity group,
			 * there is a (subtree, entity) pair with max prob. (all-max).
			 */

			for (ResolvePair pair : pairs) {
				EntityLeaf node = pair.getEntityLeaf();

				if (!matchProperty.validateMatchedPath(node))
					continue;

				subroot = pair.getXmlLeaf();
				sm = allMatchMap.get(subroot);

				if (sm == null) {
					System.out
							.println("[warn.instantiate] no subtree match for root "
									+ subroot.getXpath());
					continue;
				}

				sm.setXmlFunctionalityController(xmlFunctionalityCtrl);

				etree = entityTreeMap.get(input);

				if (etree == null) {
					System.out
							.println("[warn.instantiate] no entity tree for input "
									+ input);
					continue;
				}

				resolver.setSubtreeMatch(sm);
				resolver.setEntityTree(etree);

				group = resolver.instantiate(input, subroot);

				factGroupMap.put(input, group);
			}
		}
	}

	private void rematch() {
		subrootMapList.clear();
		subrootMapList = candidateSubrootMapList;

		matchSubtrees();
	}

	private void resolve() {
		List<EntityGroup> egroups;
		List<WSXmlLeaf> subroots;

		EntityGroupController egctrl;
		EntityResolve eresolve;
		EntityTree etree;
		Entity entity;

		Map<Input, EntityResolve> entityResolveMap = aggregator
				.getEntityResolveMap();

		for (Input input : inputs) {
			if (!subrootMapList.containsKey(input)) {
				System.out.printf("[warn.resolve] no subtrees for %s\n", input);
				continue;
			}

			if (!entityTreeMap.containsKey(input)) {
				System.out
						.printf("[warn.resolve] no entity tree for %s", input);
				continue;
			}

			eresolve = new EntityResolve();
			entityResolveMap.put(input, eresolve);

			etree = entityTreeMap.get(input);
			subroots = subrootMapList.get(input);

			egctrl = new EntityGroupController(etree);
			egctrl.init();
			eresolve.setEntityGroupController(egctrl);

			// System.out.println("EntityGroupController \n" + egctrl);

			for (int d = 0; d < egctrl.getDistance(); d++) {
				egroups = egctrl.getGroups(d);
				// log.info(String.format(
				// "Input %s, computing at distance %d, %d groups...",
				// input, d, egroups.size()));

				for (EntityGroup g : egroups) {
					// log.info(String.format("%d nodes in group %s.", g
					// .getNodes().size(), g.getKey()));

					for (EntityLeaf node : g.getNodes()) {
						entity = node.getValue().getValue();

						boolean pruned = false;

						for (String tag : SAFE_PRUNE_TAGS)
							if (entity.getValue().contains(tag)) {
								pruned = true;
								break;
							}

						if (pruned)
							continue;

						// Type type = TypeTree.findType(entity);
						//
						// if (type != null)
						// continue;

						// log.info(String.format("input %s: center node %s.",
						// input, entity));

						resolve(eresolve, etree, node, subroots);
					}
				}
			}

			if (function.isSkipped(Phrase.REST)) {
				log.info("break for trial.");
				break;
			}
		}
	}

	private void resolve(EntityResolve eresolve, EntityTree entityTree,
			EntityLeaf node, List<WSXmlLeaf> subroots) {
		SubtreeMatch sm;
		ResolvePair rpair;
		double prob;

		for (WSXmlLeaf subroot : subroots) {
			if (!matchMap.containsKey(subroot)) {
				System.out.printf("[warn] no match info for root %s\n",
						subroot.getXpath());
				continue;
			}

			sm = matchMap.get(subroot);
			sm.setXmlFunctionalityController(xmlFunctionalityCtrl);
			sm.setEntityTree(entityTree);
			sm.setCenterNode(node);
			sm.compute();

			// if (node == entityTree.getConcreteRoot()){
			// System.out.printf("Node %s Root %s \n\tSubtreeMatchs %s\n",
			// node.getValue().getValue(), subroot.getXpath(),
			// match.getMatchPairs());}

			prob = sm.getProbPositiveValidMatch();

			rpair = new ResolvePair(subroot, node);
			rpair.setProbability(prob);

			eresolve.put(node, rpair);

			if (prob > .8) {
				// System.out
				// .printf("Resolve prob %.3f node %s (%s), root %s\n\t Pairs %s\n\n",
				// prob, node.getValue().getValue(), node
				// .getParent().getValue() != null ? node
				// .getParent().getValue().getValue()
				// : "null", subroot.getXpath(), match
				// .getMatchPairs());
			}
		}
	}

	@Override
	public String save() {
		// log.info("saving");
		// System.out.println("\t\t saving");

		String res = "ok";

		if (AppXml.__DEBUG) {
			res = toString();

			UIo.saveFile(new File(dir.getDebugDir(),
					"_debug_entity_resolution_meta.html"), aggregator
					.toMetaString());

			UIo.saveFile(new File(dir.getDebugDir(),
					"_debug_entity_resolution_stat.html"), aggregator
					.toStatisticsString());

			UIo.saveFile(new File(dir.getDebugDir(),
					"_debug_entity_resolution.html"), res);

			DBInjector.save(function, toJsonString());
		}

		return res;
	}

	private void saveExperiments() {
		// log.info("saving exp");

		// save the tree to entity match semantics, for experiment 2.

		ExpFolderController efolder = ExpFolderController.shared();
		String func_name = function.getFullName();

		StringBuilder sbuilder = new StringBuilder();

		sbuilder.append(matchProperty.getType().name());
		sbuilder.append("\r\n");

		switch (matchProperty.getType()) {
		case EXISTS:
			sbuilder.append(matchProperty.getSubtreeIndex());
			break;

		case FORALL:
			sbuilder.append(matchProperty.getPredicatePath().getKey());
			break;
		}

		if (AppXml.__DEBUG) {
			UIo.saveFile(
					new File(efolder.getFolderForExp2Debug(), String.format(
							"%s_tree_to_entity.txt", func_name)), sbuilder
							.toString());
		}

		//

		Exp2TreeToEntity exp2 = new Exp2TreeToEntity();

		UIo.saveFile(
				new File(efolder.getFolderForExp2(), String.format(
						"%s_tree_to_entity.xml", func_name)),
				exp2.getXmlTree(dataGuide.getSelectedNode().getXpath(),
						matchProperty).toXmlString());

		/*
		 * save xml path to predicate path match result, for experiment 3
		 * 
		 * format: percentage, num/den, predicate_path, xpath
		 */

		if (AppXml.__DEBUG) {
			UIo.saveFile(
					new File(efolder.getFolderForExp3Debug(), String.format(
							"%s_xpath_to_predicate.txt", func_name)), resolver
							.toXmlToPredicateString());
		}

		//

		Exp3PathMatchByPair exp3 = new Exp3PathMatchByPair();
		XmlPath rootPath = dataGuide.getSelectedNode().getXpath();

		if (AppXml.__DEBUG) {
			UIo.saveFile(
					new File(efolder.getFolderForExp3DebugClean(), String
							.format("%s_xpath_to_predicate.xml", func_name)),
					exp3.getXmlTree(
							rootPath,
							matchProperty,
							new PathMatch(rootPath, resolver
									.getFactItemCounter()), false)
							.toXmlString());

			UIo.saveFile(new File(efolder.getFolderForExp3DebugCleanDebug(),
					String.format("%s_xpath_to_predicate.txt", func_name)),
					resolver.toXmlToPredicateDebugString());
		}

		UIo.saveFile(
				new File(efolder.getFolderForExp3(), String.format(
						"%s_xpath_to_predicate.xml", func_name)),
				exp3.getXmlTree(rootPath, matchProperty,
						new PathMatch(rootPath, resolver.getFactItemCounter()),
						true).toXmlString());

		//

		File f5xml = efolder.getFolderForExp5();

		File wsdir = new File(f5xml, function.getService());

		if (!wsdir.exists())
			wsdir.mkdir();

		File fdir = new File(wsdir, function.getName());

		if (!fdir.exists())
			fdir.mkdir();

		for (Entry<Input, WSXmlTree> e : subtreeController.getXmlTreeMap()
				.entrySet()) {
			WSXmlTree tree = e.getValue();
			tree.addPrefix("t", "http://temp");

			String fname = e.getKey().toFilename();
			File f = new File(fdir, String.format("exp2b_%s.xml", fname));

			UIo.saveFile(f, tree.toXmlString());
		}
	}

	private String toJsonString() {
		FactSet facts = new FactSet();

		for (Entry<Input, FactSet> e : factGroupMap.entrySet())
			for (Fact f : e.getValue())
				facts.add(f);

		return facts.toJsonString();
	}

	@Override
	public String toString() {
		Html html = new Html(dir.getServiceName() + "/" + function.getName());

		if (matchProperty != null) {
			html.addElement(matchProperty.toHtmlElement());
			html.newline();
		}

		VariableManager varManager = resolver.getVariableManager();

		html.addElement(varManager.toVariableHtmlTable());
		html.newline();

		html.addElement(varManager.toVariableEntityHtmlTable());
		html.newline();

		html.addElement(resolver.getFuser().toHtmlTable());
		html.newline();

		// html.addElement(resolver.toFactItemHtmlTable());
		// html.newline();

		html.addElement(pcaConfidence.toHtmlTable());
		html.newline();

		html.addElement(resolver.toTmpFactItemHtmlTable());
		html.newline();

		html.addElement(toTripleHtmlTable());
		html.newline();

		return html.toString();
	}

	private HtmlTable toTripleHtmlTable() {
		TableData data = new TableData();
		TableTuple tuple = new TableTuple();
		tuple.add("Input", "Triples");
		data.add(tuple);

		StringBuilder sb = new StringBuilder();

		for (Entry<Input, FactSet> e : factGroupMap.entrySet()) {
			tuple = new TableTuple();

			tuple.add(e.getKey().getValue());

			sb.delete(0, sb.length());

			for (Fact f : e.getValue()) {
				sb.append(f.toHtmlString());
				sb.append("<br>");
			}

			tuple.add(sb.toString());
			data.add(tuple);
		}

		return new HtmlTable("Triples", data, false);
	}

}
